﻿// Copyright © 2024 By HeBianGu(QQ:908293466) https://github.com/HeBianGu/WPF-Control

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Data;
using System.Windows.Input;


namespace H.Controls.PropertyGrid
{
    internal abstract class ObjectContainerHelperBase : ContainerHelperBase
    {
        // This is needed to work around the ItemsControl behavior.
        // When ItemsControl is preparing its containers, it appears 
        // that calling Refresh() on the CollectionView bound to
        // the ItemsSource prevents the items from being displayed.
        // This patch is to avoid such a behavior.
        private bool _isPreparingItemFlag = false;
        private PropertyItemCollection _propertyItemCollection;

        public ObjectContainerHelperBase(IPropertyContainer propertyContainer)
          : base(propertyContainer)
        {
            _propertyItemCollection = new PropertyItemCollection(new ObservableCollection<PropertyItem>());
            UpdateFilter();
            UpdateCategorization(false);

        }

        public override IList Properties
        {
            get { return _propertyItemCollection; }
        }

        private PropertyItem DefaultProperty
        {
            get
            {
                PropertyItem defaultProperty = null;
                string defaultName = this.GetDefaultPropertyName();
                if (defaultName != null)
                {
                    defaultProperty = _propertyItemCollection
                      .FirstOrDefault((prop) => object.Equals(defaultName, prop.PropertyDescriptor.Name));
                }

                return defaultProperty;
            }
        }


        protected PropertyItemCollection PropertyItems
        {
            get
            {
                return _propertyItemCollection;
            }
        }

        public override PropertyItemBase ContainerFromItem(object item)
        {
            if (item == null)
                return null;
            // Exception case for ObjectContainerHelperBase. The "Item" may sometimes
            // be identified as a string representing the property name or
            // the PropertyItem itself.
            Debug.Assert(item is PropertyItem || item is string);

            PropertyItem propertyItem = item as PropertyItem;
            if (propertyItem != null)
                return propertyItem;


            string propertyStr = item as string;
            if (propertyStr != null)
                return this.PropertyItems.FirstOrDefault((prop) => propertyStr == prop.PropertyDescriptor.Name);

            return null;
        }

        public override object ItemFromContainer(PropertyItemBase container)
        {
            // Since this call is only used to update the PropertyGrid.SelectedProperty property,
            // return the PropertyName.
            PropertyItem propertyItem = container as PropertyItem;
            if (propertyItem == null)
                return null;

            return propertyItem.PropertyDescriptor.Name;
        }

        public override void UpdateValuesFromSource()
        {
            foreach (PropertyItem item in this.PropertyItems)
            {
                item.DescriptorDefinition.UpdateValueFromSource();
                item.ContainerHelper.UpdateValuesFromSource();
            }
        }

        public void GenerateProperties()
        {
            if (this.PropertyItems.Count == 0
              )
            {
                this.RegenerateProperties();
            }
        }

        protected override void OnFilterChanged()
        {
            this.UpdateFilter();
        }

        protected override void OnCategorizationChanged()
        {
            UpdateCategorization(true);
        }

        protected override void OnAutoGeneratePropertiesChanged()
        {
            this.RegenerateProperties();
        }

        protected override void OnHideInheritedPropertiesChanged()
        {
            this.RegenerateProperties();
        }

        protected override void OnEditorDefinitionsChanged()
        {
            this.RegenerateProperties();
        }

        protected override void OnPropertyDefinitionsChanged()
        {
            this.RegenerateProperties();
        }






        protected internal override void SetPropertiesExpansion(bool isExpanded)
        {
            if (this.Properties.Count == 0)
            {
                this.GenerateProperties();
            }

            base.SetPropertiesExpansion(isExpanded);
        }

        protected internal override void SetPropertiesExpansion(string propertyName, bool isExpanded)
        {
            if (this.Properties.Count == 0)
            {
                this.GenerateProperties();
            }

            base.SetPropertiesExpansion(propertyName, isExpanded);
        }

        private void UpdateFilter()
        {
            FilterInfo filterInfo = this.PropertyContainer.FilterInfo;

            this.PropertyItems.FilterPredicate = filterInfo.Predicate
              ?? PropertyItemCollection.CreateFilter(filterInfo.InputString, this.PropertyItems, this.PropertyContainer);
        }

        private void UpdateCategorization(bool updateSubPropertiesCategorization)
        {
            _propertyItemCollection.UpdateCategorization(this.ComputeCategoryGroupDescription(), this.PropertyContainer.IsCategorized, this.PropertyContainer.IsSortedAlphabetically);
            if (updateSubPropertiesCategorization && (_propertyItemCollection.Count > 0))
            {
                foreach (PropertyItem propertyItem in _propertyItemCollection)
                {
                    PropertyItemCollection subPropertyItemsCollection = propertyItem.Properties as PropertyItemCollection;
                    if (subPropertyItemsCollection != null)
                    {
                        subPropertyItemsCollection.UpdateCategorization(this.ComputeCategoryGroupDescription(), this.PropertyContainer.IsCategorized, this.PropertyContainer.IsSortedAlphabetically);
                    }
                }
            }
        }

        private GroupDescription ComputeCategoryGroupDescription()
        {
            if (!PropertyContainer.IsCategorized)
                return null;
            return new PropertyGroupDescription(PropertyItemCollection.CategoryPropertyName);
        }

        private string GetCategoryGroupingPropertyName()
        {
            PropertyGroupDescription propGroup = this.ComputeCategoryGroupDescription() as PropertyGroupDescription;
            return (propGroup != null) ? propGroup.PropertyName : null;
        }

        private void OnChildrenPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (ObjectContainerHelperBase.IsItemOrderingProperty(e.PropertyName)
              || this.GetCategoryGroupingPropertyName() == e.PropertyName)
            {
                // Refreshing the view while Containers are generated will throw an exception
                if (this.ChildrenItemsControl.ItemContainerGenerator.Status != GeneratorStatus.GeneratingContainers
                  && !_isPreparingItemFlag)
                {
                    this.PropertyItems.RefreshView();
                }
            }
        }

        protected abstract string GetDefaultPropertyName();

        protected abstract IEnumerable<PropertyItem> GenerateSubPropertiesCore();

        private void RegenerateProperties()
        {
            IEnumerable<PropertyItem> subProperties = this.GenerateSubPropertiesCore();

            foreach (PropertyItem propertyItem in subProperties)
            {
                this.InitializePropertyItem(propertyItem);
            }

            //Remove the event callback from the previous children (if any)
            foreach (PropertyItem propertyItem in this.PropertyItems)
            {
                propertyItem.PropertyChanged -= OnChildrenPropertyChanged;
            }

            this.PropertyItems.UpdateItems(subProperties);

            //Add the event callback to the new childrens
            foreach (PropertyItem propertyItem in this.PropertyItems)
            {
                propertyItem.PropertyChanged += OnChildrenPropertyChanged;
            }

            // Update the selected property on the property grid only.
            PropertyGrid propertyGrid = PropertyContainer as PropertyGrid;
            if (propertyGrid != null)
            {
                propertyGrid.SelectedPropertyItem = this.DefaultProperty;
            }
        }

        protected static List<PropertyDescriptor> GetPropertyDescriptors(object instance, bool hideInheritedProperties)
        {
            PropertyDescriptorCollection descriptors;

            TypeConverter tc = TypeDescriptor.GetConverter(instance);
            if (tc == null || !tc.GetPropertiesSupported())
            {
                if (instance is ICustomTypeDescriptor)
                {
                    descriptors = ((ICustomTypeDescriptor)instance).GetProperties();
                }
                //ICustomTypeProvider is only available in .net 4.5 and over. Use reflection so the .net 4.0 and .net 3.5 still works.
                else if (instance.GetType().GetInterface("ICustomTypeProvider", true) != null)
                {
                    System.Reflection.MethodInfo methodInfo = instance.GetType().GetMethod("GetCustomType");
                    Type result = methodInfo.Invoke(instance, null) as Type;
                    descriptors = TypeDescriptor.GetProperties(result);
                }
                else
                {
                    descriptors = TypeDescriptor.GetProperties(instance.GetType());
                }
            }
            else
            {
                descriptors = tc.GetProperties(instance);
            }

            if (descriptors != null)
            {
                IEnumerable<PropertyDescriptor> descriptorsProperties = descriptors.Cast<PropertyDescriptor>();
                if (hideInheritedProperties)
                {
                    IEnumerable<PropertyDescriptor> properties = from p in descriptorsProperties
                                                                 where p.ComponentType == instance.GetType()
                                                                 select p;
                    return properties.ToList();
                }
                else
                {
                    return descriptorsProperties.ToList();
                }
            }

            return null;
        }




        protected bool GetWillRefreshPropertyGrid(PropertyDescriptor propertyDescriptor)
        {
            if (propertyDescriptor == null)
                return false;

            RefreshPropertiesAttribute attribute = PropertyGridUtilities.GetAttribute<RefreshPropertiesAttribute>(propertyDescriptor);
            if (attribute != null)
                return attribute.RefreshProperties != RefreshProperties.None;

            return false;
        }

        internal void InitializeDescriptorDefinition(
          DescriptorPropertyDefinitionBase descriptorDef,
          PropertyDefinition propertyDefinition)
        {
            if (descriptorDef == null)
                throw new ArgumentNullException("descriptorDef");

            if (propertyDefinition == null)
                return;

            // Values defined on PropertyDefinition have priority on the attributes
            if (propertyDefinition != null)
            {
                if (propertyDefinition.Category != null)
                {
                    descriptorDef.Category = propertyDefinition.Category;
                    descriptorDef.CategoryValue = propertyDefinition.Category;
                }

                if (propertyDefinition.Description != null)
                {
                    descriptorDef.Description = propertyDefinition.Description;
                }
                if (propertyDefinition.DisplayName != null)
                {
                    descriptorDef.DisplayName = propertyDefinition.DisplayName;
                }

                if (propertyDefinition.DisplayOrder != null)
                {
                    descriptorDef.DisplayOrder = propertyDefinition.DisplayOrder.Value;
                }

                if (propertyDefinition.IsExpandable != null)
                {
                    descriptorDef.ExpandableAttribute = propertyDefinition.IsExpandable.Value;
                }
            }
        }

        private void InitializePropertyItem(PropertyItem propertyItem)
        {
            DescriptorPropertyDefinitionBase pd = propertyItem.DescriptorDefinition;
            propertyItem.PropertyDescriptor = pd.PropertyDescriptor;

            propertyItem.IsReadOnly = pd.IsReadOnly;
            propertyItem.DisplayName = pd.DisplayName;
            propertyItem.Description = pd.Description;

            propertyItem.Category = pd.Category;
            propertyItem.PropertyOrder = pd.DisplayOrder;

            //These properties can vary with the value. They need to be bound.
            if (pd.PropertyDescriptor.Converter is ExpandableObjectConverter)
            {
                propertyItem.IsExpandable = true;
            }
            else
            {
                SetupDefinitionBinding(propertyItem, PropertyItemBase.IsExpandableProperty, pd, () => pd.IsExpandable, BindingMode.OneWay);
            }
            SetupDefinitionBinding(propertyItem, PropertyItemBase.AdvancedOptionsIconProperty, pd, () => pd.AdvancedOptionsIcon, BindingMode.OneWay);
            SetupDefinitionBinding(propertyItem, PropertyItemBase.AdvancedOptionsTooltipProperty, pd, () => pd.AdvancedOptionsTooltip, BindingMode.OneWay);
            SetupDefinitionBinding(propertyItem, PropertyItem.ValueProperty, pd, () => pd.Value, BindingMode.TwoWay);

            if (pd.CommandBindings != null)
            {
                foreach (CommandBinding commandBinding in pd.CommandBindings)
                {
                    propertyItem.CommandBindings.Add(commandBinding);
                }
            }

            // PropertyItem.PropertyType's defaultValue equals current PropertyItem's value => set the DefaultValue attribute
            if (pd.DefaultValue != null)
            {
                object typeDefaultValue = this.GetTypeDefaultValue(propertyItem.PropertyType);

                if (((propertyItem.Value != null) && propertyItem.Value.Equals(typeDefaultValue))
                      || ((propertyItem.Value == null) && (typeDefaultValue == propertyItem.Value)))
                {
#if VS2008
        propertyItem.Value = pd.DefaultValue;
#else
                    propertyItem.SetCurrentValue(PropertyItem.ValueProperty, pd.DefaultValue);
#endif
                }
            }
        }

        private object GetTypeDefaultValue(Type type)
        {
            if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
            {
                type = type.GetProperty("Value").PropertyType;
            }

            return type.IsValueType ? Activator.CreateInstance(type) : null;
        }

        private void SetupDefinitionBinding<T>(
          PropertyItem propertyItem,
          DependencyProperty itemProperty,
          DescriptorPropertyDefinitionBase pd,
          Expression<Func<T>> definitionProperty,
          BindingMode bindingMode)
        {
            string sourceProperty = ReflectionHelper.GetPropertyOrFieldName(definitionProperty);
            Binding binding = new Binding(sourceProperty)
            {
                Source = pd,
                Mode = bindingMode
            };

            propertyItem.SetBinding(itemProperty, binding);
        }

        internal FrameworkElement GenerateChildrenEditorElement(PropertyItem propertyItem)
        {
            FrameworkElement editorElement = null;
            DescriptorPropertyDefinitionBase pd = propertyItem.DescriptorDefinition;
            object definitionKey = null;
            Type definitionKeyAsType = definitionKey as Type;
            ITypeEditor editor = null;

            if (editor == null)
                editor = pd.CreateAttributeEditor();

            if (editor != null)
                editorElement = editor.ResolveEditor(propertyItem);


            if ((editorElement == null) && (definitionKey == null) && (propertyItem.PropertyDescriptor != null))
                editorElement = this.GenerateCustomEditingElement(propertyItem.PropertyDescriptor.Name, propertyItem);

            if (editorElement == null && definitionKeyAsType == null)
                editorElement = this.GenerateCustomEditingElement(propertyItem.PropertyType, propertyItem);

            if (editorElement == null)
            {
                if (pd.IsReadOnly)
                    editor = new TextBlockEditor();

                // Fallback: Use a default type editor.
                if (editor == null)
                {
                    editor = (definitionKeyAsType != null)
                    ? PropertyGridUtilities.CreateDefaultEditor(definitionKeyAsType, null, propertyItem)
                    : pd.CreateDefaultEditor(propertyItem);
                }

                Debug.Assert(editor != null);

                editorElement = editor.ResolveEditor(propertyItem);
            }

            return editorElement;
        }

        internal PropertyDefinition GetPropertyDefinition(PropertyDescriptor descriptor)
        {
            PropertyDefinition def = null;

            PropertyDefinitionCollection propertyDefs = this.PropertyContainer.PropertyDefinitions;
            if (propertyDefs != null)
            {
                def = propertyDefs[descriptor.Name];
                if (def == null)
                {
                    def = propertyDefs.GetRecursiveBaseTypes(descriptor.PropertyType);
                }
            }

            return def;
        }


        public override void PrepareChildrenPropertyItem(PropertyItemBase propertyItem, object item)
        {
            _isPreparingItemFlag = true;
            base.PrepareChildrenPropertyItem(propertyItem, item);

            if (propertyItem.Editor == null)
            {
                FrameworkElement editor = this.GenerateChildrenEditorElement((PropertyItem)propertyItem);
                if (editor != null)
                {
                    // Tag the editor as generated to know if we should clear it.
                    ContainerHelperBase.SetIsGenerated(editor, true);
                    propertyItem.Editor = editor;
                }
            }
            _isPreparingItemFlag = false;
        }

        public override void ClearChildrenPropertyItem(PropertyItemBase propertyItem, object item)
        {
            if (propertyItem.Editor != null
              && ContainerHelperBase.GetIsGenerated(propertyItem.Editor))
            {
                propertyItem.Editor = null;
            }

            base.ClearChildrenPropertyItem(propertyItem, item);
        }

        public override Binding CreateChildrenDefaultBinding(PropertyItemBase propertyItem)
        {
            Binding binding = new Binding("Value");
            binding.Mode = ((PropertyItem)propertyItem).IsReadOnly ? BindingMode.OneWay : BindingMode.TwoWay;
            return binding;
        }

        protected static string GetDefaultPropertyName(object instance)
        {
            AttributeCollection attributes = TypeDescriptor.GetAttributes(instance);
            DefaultPropertyAttribute defaultPropertyAttribute = (DefaultPropertyAttribute)attributes[typeof(DefaultPropertyAttribute)];
            return defaultPropertyAttribute != null ? defaultPropertyAttribute.Name : null;
        }

        private static bool IsItemOrderingProperty(string propertyName)
        {
            return string.Equals(propertyName, PropertyItemCollection.DisplayNamePropertyName)
              || string.Equals(propertyName, PropertyItemCollection.CategoryOrderPropertyName)
              || string.Equals(propertyName, PropertyItemCollection.PropertyOrderPropertyName);
        }

    }
}
